﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WFA
{
    public class FuzzyController
    {
        private FuzzyItem[,] _items;
        private int _k;
        private Func<float, float, float> _function;

        private float _a, _c;

        public static double IntegralStep = 0.01;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="function"></param>
        /// <param name="k"></param>
        /// <param name="xStart"></param>
        /// <param name="xEnd"></param>
        public FuzzyController(Func<float, float, float> function, int k = 10, float xStart = 1, float xEnd = 5)
        {
            _function = function;
            K = k;
            _a = xStart;
            _c = xEnd;
        }


        /// <summary>
        /// Minimalna wartość (początek) przedziału OZ
        /// </summary>
        public static double OZFrom;

        /// <summary>
        /// Maksymalna wartość (koniec) przedziału OZ
        /// </summary>
        public static double OZTo;

        public FuzzyItem[,] FuzzyItems { get { return _items; } }

        public float StartAt
        {
            get { return _a; }
            set { _a = value; }
        }

        public float EndAt { get { return _c; } set { _c = value; } }

        public Func<float, float, float> Function
        {
            get { return _function; }
            set { _function = value; }
        }


        /// <summary>
        /// Zwraca liczbę pokryć zbioru dziedziny zmiennych
        /// </summary>
        public int K
        {
            get { return _k; }
            set
            {

                if (_items != null) GC.SuppressFinalize(_items);
                _k = value;
                _items = new FuzzyItem[_k + 1, _k + 1];

                for (int j = 0; j < _k; j++)
                {
                    for (int i = 0; i < _k; i++)
                    {
                        _items[i, j] = null;
                    }
                }
            }
        }

        public void Learn(float x1, float x2)
        {
            // przynależność X1
            MembershipStruct? ux1 = null;
            foreach (var item in Functions.Membership(_a, _c, _k, x1))
            {
                // funkcja przynależności może zwrócić kilka przynależności
                // wybieramy przynależność wyższą
                if ((ux1 == null) || (ux1.Value.Value < item.Value))
                {
                    ux1 = item;
                }
            }

            // przynależność X2
            MembershipStruct? ux2 = null;
            foreach (var item in Functions.Membership(_a, _c, _k, x2))
            {
                // funkcja przynależności może zwrócić kilka przynależności
                // wybieramy przynależność wyższą
                if ((ux2 == null) || (ux2.Value.Value < item.Value))
                {
                    ux2 = item;
                }
            }

            // przynależność Y
            float y = _function(x1, x2);
            MembershipStruct? uy = null;
            foreach (var item in Functions.Membership(FuzzyController.OZFrom, FuzzyController.OZTo, _k, y))
            {
                // funkcja przynależności może zwrócić kilka przynależności
                // wybieramy przynależność wyższą
                if ((uy == null) || (uy.Value.Value < item.Value))
                {
                    uy = item;
                }
            }

            if ((ux1 == null) || (ux2 == null) || (uy == null))
            {
                return;
                throw new Exception("Nie znaleziono przynależności do zbioru.");
            }

            // stopień przynależności reguły 
            double ruleMembership = ux1.Value.Value * ux2.Value.Value * uy.Value.Value;


            if ((_items[ux1.Value.A, ux2.Value.A] == null) || (_items[ux1.Value.A, ux2.Value.A].Value < ruleMembership))
            {
                _items[ux1.Value.A, ux2.Value.A] = new FuzzyItem
                {
                    X1 = ux1.Value,
                    X2 = ux2.Value,
                    Y = uy.Value,
                    Value = ruleMembership
                };
            }
        }

        public static int SharpeningFunction
        {
            get;
            set;
        }

        public double? Sharpen(float x1, float x2)
        {
            if (SharpeningFunction == 0)
            {
                return CenterAverageDefuzzification(x1, x2);
            }

            return CenterOfGravity(x1, x2);
        }

        public static string Gettitle(int sharpID)
        {
            switch (sharpID)
            {
                case 0: return "Center average defuzzification";
                case 1: return "Center of gravity";
            }

            return "Unknown";
        }

        #region Metody wyostrzania

        public float? CenterOfGravity(float x1, float x2)
        {
            List<MembershipStruct> ux1ms = Functions.Membership(_a, _c, _k, x1).ToList();

            List<MembershipStruct> ux2ms = Functions.Membership(_a, _c, _k, x2).ToList();

            double licznik = 0;
            double mianownik = 0;

            List<FuzzyItem> rF = new List<FuzzyItem>();

            foreach (var ux1 in ux1ms)
            {
                foreach (var ux2 in ux2ms)
                {
                    var y = _items[ux1.A, ux2.A];

                    if (y == null) continue;

                    rF.Add(y);
                }
            }

            // otrzymujemy wszystkie reguły kryjące wynikową wartość (np. R1, R2, R3)
            var f = from c in rF orderby c.Y.A select c;
            // najmniejszy numer zbioru (np R1)
            int minR = f.Min(c => c.Y.A);
            // największy numer zbioru (np R3)
            int maxR = f.Max(c => c.Y.A);

            double left, right, temp;
            // pobieramy jaki przedział jest przeszukiwany (np od 0.5 do 0.7)
            Functions.RuleDomain(minR, FuzzyController.OZFrom, FuzzyController.OZTo, _k, out left, out temp);
            Functions.RuleDomain(maxR, FuzzyController.OZFrom, FuzzyController.OZTo, _k, out temp, out right);

            licznik = 0;
            mianownik = 0;

            double x = left;
            while (x <= right)
            {
                double max = 0;
                foreach (var rule in f)
                {
                    double a = Functions.RuleMembership(rule.Y.A, x, FuzzyController.OZFrom, FuzzyController.OZTo, _k);

                    if (a > rule.Y.Value) a = rule.Y.Value;
                    if (a > max) max = a;
                }

                licznik += max * x;
                mianownik += max;

                x = Math.Round(x + IntegralStep, 5);
            }

            return (float)(licznik / mianownik);
        }

        /// <summary>
        /// Metoda zwraca wartość funkcji (nauczonej z bazy reguł)
        /// </summary>
        /// <param name="x1"></param>
        /// <param name="x2"></param>
        /// <returns>Wyostrzoną wartość</returns>
        public double? CenterAverageDefuzzification(float x1, float x2)
        {

            List<MembershipStruct> ux1ms = Functions.Membership(_a, _c, _k, x1).ToList();

            List<MembershipStruct> ux2ms = Functions.Membership(_a, _c, _k, x2).ToList();

            double licznik = 0;
            double mianownik = 0;
            foreach (var ux1 in ux1ms)
            {
                foreach (var ux2 in ux2ms)
                {
                    var y = _items[ux1.A, ux2.A];

                    if (y == null) continue;

                    licznik += ux1.Value * ux2.Value * y.Y.Max;
                    mianownik += ux1.Value * ux2.Value;
                }
            }

            if (mianownik == 0) return null;
            return licznik / mianownik;
        }
        #endregion
    }

    public class FuzzyItem
    {
        /// <summary>
        /// Przynależność do dziedziny X1
        /// </summary>
        public MembershipStruct X1 { get; set; }

        /// <summary>
        /// Przynależność do dziedziny X2
        /// </summary>
        public MembershipStruct X2 { get; set; }

        /// <summary>
        /// Przynależność do dziedziny Y
        /// </summary>
        public MembershipStruct Y { get; set; }

        /// <summary>
        /// Wartość (ostra)
        /// </summary>
        public double Value { get; set; }

        public override string ToString()
        {
            return "Y:" + Y.Max;
        }

    }
}
